
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QtSerialPort>
#include <QMessageBox>
#include <QDateTime>
#include <QFileDialog>  //打开文件头文件
#include <QStandardPaths>   //文件路径

MainWindow::MainWindow(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    init();
    /*将信号ackOK与槽函数sendOnceData绑定*/
    connect(this, &MainWindow::ackOK, this, &MainWindow::sendOnceData);
}

MainWindow::~MainWindow()
{
    delete ui;
}
/*初始化界面*/
void MainWindow::init()
{
    setWindowFlags(windowFlags()& ~Qt::WindowMaximizeButtonHint);//禁止窗口最大化
    setFixedSize(this->width(), this->height());                 //禁止改变窗口大小
    ui->startDownloadBtn->setDisabled(true);                     //初始化是不允许点击下载按钮
    ui->downloadingBar->setValue(0);                             //设置进度条0%
    ui->sendSizeEdt->setValidator(new QIntValidator(1, 65535, this));  //限制输入大小，最大64k
    ui->sendSizeEdt->setText(QString::number(2048));             //设置一次默认发送1k数据
    /*获取有用的串口*/
    auto portsInof = QSerialPortInfo::availablePorts();
    for(auto & info : portsInof)
    {
        qInfo()<<info.description()<<info.portName()<<info.systemLocation();
        /*将可用端口显示在下拉列表中*/
        ui->portSelectCmd->addItem(info.portName(),info.portName());
    }
    /*获取标准的波特率*/
    auto baudRates = QSerialPortInfo::standardBaudRates();
    for(auto rate : baudRates)
    {
        /*将波特率显示在下拉列表中*/
        ui->baudRateCmd->addItem(QString::number(rate), rate);
    }
    /*设置默认波特率*/
    ui->baudRateCmd->setCurrentText("9600");

    downloadState = DOWNLOAD_INVALID;   //初始化下载状态
    undownloadByte = 0;
    downloadedByte = 0;

    QByteArray t;
    char tbcc = 0;
    t.append("\xC5");
    t.append("\x5C");
    t.append("\x00");
    t.append("\x00");
    t.append("\x01");
    t.append("\xff");
    /*求异或校验位*/
    for(int i = 2; i < t.size(); i ++)
    {
        tbcc ^= t.at(i) & 0xff;
    }
//    for(int i = 2; i < sendBuf.size(); i++)
//    {
//        bcc ^= sendBuf.at(i) & 0xff;
//    }
    t.append(tbcc);
    qInfo()<<"0x"<<t.toHex();
    if(tbcc != (t.at(t.size() - 1)))
    {
        qInfo()<<"!=";
    }
    else
    {
        qInfo()<<"=";
    }

}

/*清空发送信息日志*/
void MainWindow::on_clearSendMsgBtn_released()
{
    ui->sendMsgDisEdt->clear();
}

/*清空接收信息日志*/
void MainWindow::on_clearAckBtn_released()
{
    ui->receiveAckEdt->clear();
}

/*打开串口按钮*/
void MainWindow::on_openPortBtn_released()
{
    /*判断串口是否已打开，如果已打开就关闭串口*/
    if(mySerialPort.isOpen())
    {
        mySerialPort.close();
        ui->openPortBtn->setText("打开串口");
        QDateTime current_date_time = QDateTime::currentDateTime();//获取系统时间
        QString current_time = current_date_time.toString("hh:mm:ss");
        ui->sendMsgDisEdt->append(current_time + " : " + mySerialPort.portName() + " already close");
        ui->startDownloadBtn->setDisabled(true);               //关闭串口禁止下载
        undownloadByte = 0;
        downloadedByte = 0;
        curDownloadDataLen = 0;
        return;
    }

    auto portName  = ui->portSelectCmd->currentData().toString();                   //获取串口名称
    auto baundRate = ui->baudRateCmd->currentData().value<QSerialPort::BaudRate>(); //获取波特率

    mySerialPort.setPortName(portName);
    mySerialPort.setBaudRate(baundRate);
    /*以下标志位采用默认值*/
    mySerialPort.setDataBits(QSerialPort::Data8);           //数据位
    mySerialPort.setFlowControl(QSerialPort::NoFlowControl);//无流控制
    mySerialPort.setParity(QSerialPort::NoParity);          //校验位
    mySerialPort.setStopBits(QSerialPort::OneStop);         //停止位
    /*打开串口(以读写的方式)*/
    if(!mySerialPort.open(QIODevice::ReadWrite))
    {
        /*/打开失败*/
        QDateTime current_date_time = QDateTime::currentDateTime();//获取系统时间
        QString current_time = current_date_time.toString("hh:mm:ss");
        ui->sendMsgDisEdt->append(current_time + " : " + portName + " open failed," + mySerialPort.errorString());

        QMessageBox::warning(this, "warning", portName + " open failed:" + mySerialPort.errorString());
    }
    else
    {
        /*打开成功*/
        QDateTime current_date_time = QDateTime::currentDateTime();//获取系统时间
        QString current_time = current_date_time.toString("hh:mm:ss");
        ui->sendMsgDisEdt->append(current_time + " : " + portName + " open success");

        ui->openPortBtn->setText("关闭串口");
        ui->startDownloadBtn->setDisabled(false);               //允许下载
        /*关联接收函数*/
        connect(&mySerialPort, &QSerialPort::readyRead, this, &MainWindow::onReadyReadCom);
    }

}

/*下载按钮*/
void MainWindow::on_startDownloadBtn_released()
{

    QDateTime current_date_time = QDateTime::currentDateTime();//获取系统时间
    QString current_time = current_date_time.toString("hh:mm:ss");

    dataOnceLen = ui->sendSizeEdt->text().toInt();
    qInfo()<<"once len"<<dataOnceLen;
    ui->hasSendSizeLab->clear();
    if((dataOnceLen < 1) || (dataOnceLen > 65536))
    {
        ui->sendMsgDisEdt->append(current_time + " : 单次发送数据大小有误（0<x<65536）");
        QMessageBox::warning(this, "warning", " 单次发送数据大小有误（0<x<65536）");
    }
    else
    {
        ui->sendMsgDisEdt->append(current_time + " : start send data, once data len:" + QString::number(dataOnceLen));
        send_start_signal();
    }

}
/*发送开始数据帧
    开始帧：0xC5 0x5C 0x01 0x00 0x00 bcc 0x5C 0xC5
*/
void MainWindow::send_start_signal()
{
    QByteArray sendBuf;
    char bcc = 0x00;

    sendBuf.append("\xC5\x5C");
    sendBuf.append(static_cast<char>(1));   //开始帧标志位
    sendBuf.append(static_cast<char>(0));   //帧数据长度高字节
    sendBuf.append(static_cast<char>(0));   //帧数据长度底字节
    for(int i = 2; i < sendBuf.size(); i++)
    {
        bcc ^= sendBuf.at(i) & 0xff;
    }
    sendBuf.append(bcc);                    //校验位
    sendBuf.append("\x5C\xC5");
    mySerialPort.write(sendBuf);            //发送数据
    downloadState = DOWNLOAD_START_WAIT_ACK;//发送标志位
}
/*发送结束数据帧
    开始帧：0xC5 0x5C 0x01 0x00 0x00 bcc 0x5C 0xC5
*/
void MainWindow::send_end_signal()
{
    QByteArray sendBuf;
    char bcc = 0x00;

    sendBuf.append("\xC5\x5C");
    sendBuf.append(static_cast<char>(2));   //结束帧标志位
    sendBuf.append(static_cast<char>(0));   //帧数据长度高字节
    sendBuf.append(static_cast<char>(0));   //帧数据长度底字节
    for(int i = 2; i < sendBuf.size(); i++)
    {
        bcc ^= sendBuf.at(i) & 0xff;
    }
    sendBuf.append(bcc);                    //校验位
    sendBuf.append("\x5C\xC5");
    mySerialPort.write(sendBuf);            //发送数据
}

/*打开文件*/
void MainWindow::on_openFileBtn_released()
{
    QSettings setting("./lastopenFile.ini", QSettings::IniFormat);
    QString lastPath = setting.value("LastFilePath").toString();

    /*默认打开文件的路径为桌面*/
    auto openFileName = QFileDialog::getOpenFileName(this, "bin file", lastPath, "bin File(*.bin)");
    QDateTime current_date_time = QDateTime::currentDateTime();//获取系统时间
    QString current_time = current_date_time.toString("hh:mm:ss");
    if(!openFileName.isEmpty())
    {
        setting.setValue("LastFilePath", openFileName);
        QFile  file(openFileName);
        //以只读的方式打开文件
        if(file.open(QIODevice::ReadOnly))
        {
            ui->openFileEdt->setText(openFileName);
            ui->sendMsgDisEdt->append(current_time + " : open file " + openFileName );
            ui->sendMsgDisEdt->append(current_time + " : bin file size " + QString::number(file.size()) + "Byte");
            ui->dataSizeLab->setText(QString::number(file.size())); //显示文件大小

            binDataArray.clear();//先清空上次留下的数据
            binDataArray = file.readAll();//读取数据
            binDataLen = file.size();                           //获取bin文件的大小
            undownloadByte = binDataLen;
        }
         else
        {
            ui->sendMsgDisEdt->append(current_time + " : open file failed");
            ui->startDownloadBtn->setDisabled(true);
        }

    }
    else
    {
        ui->sendMsgDisEdt->append(current_time + " : open file failed, the file isEmpty");
        ui->startDownloadBtn->setDisabled(true);
    }
}
/*读取串口发送的数据
  err code
    0x01：帧头错误
    0x02：校验错误
    0x04：帧尾错误
    0x08：数据长度错误
*/
void MainWindow::onReadyReadCom()
{

    QByteArray comData = mySerialPort.readAll();//读取串口中的二进制文件
    uint16_t len;
    char bcc = 0;
    uint8_t err = 0x00;
    QDateTime current_date_time = QDateTime::currentDateTime();//获取系统时间
    QString current_time = current_date_time.toString("hh:mm:ss");
    if(!comData.isEmpty())
    {
//        qInfo()<<"receive data size "<< comData.size();
//        for(int temp = 0; temp < comData.size(); temp++)
//        {
//            qInfo()<<"0x"<<comData.toHex();
//        }
        /*判断帧头是否有误*/
        if((comData.at(0) & 0xff) != 0xC5)
        {
            err |=0x01;
        }
        if((comData.at(1) & 0xff) != 0x5C)
        {
            err |=0x01;
        }
        /*读取帧数据长度*/
        len = (comData.at(3) & 0xff) * 0xff + (comData.at(4) & 0xff);
//        qInfo()<<"receive data len "<< len;
        if(len > comData.size())//数据长度大于包的长度，说明解析有误
        {
            err |=0x08;
        }
        else
        {
            /*求异或校验位*/
            for(int i = 2; i < 5 + len; i ++)
            {
                bcc ^= (comData.at(i) & 0xff);
            }
            if(bcc != (comData.at(5 + len)))// & 0xff
            {
                err |= 0x02;//校验出错
            }
            /*判断帧尾是否有误*/
            if((comData.at(6 + len) & 0xff) != 0x5C)
            {
                err |=0x04;
            }
            if((comData.at(7 + len) & 0xff) != 0xC5)
            {
                err |=0x04;
            }
        }

        if(err != 0)
        {   qInfo()<<"err code: "<< err;
            if(err & 0x01)
            {
                ui->receiveAckEdt->append(current_time + " : 帧头错误！");
            }
            else if(err & 0x02)
            {
                ui->receiveAckEdt->append(current_time + " : 校验错误！");
                qInfo()<<"接收到 bcc: "<<QString::number(comData.at(5 + len)&0xFF) <<"计算 bcc: "<<QString::number(bcc);
            }
            else if(err & 0x04)
            {
                ui->receiveAckEdt->append(current_time + " : 帧尾错误！");
            }
            else if(err & 0x08)
            {
                ui->receiveAckEdt->append(current_time + " : 数据包长度错误");
            }
            else
            {
                ui->receiveAckEdt->append(current_time + " : 未知错误！err code：" + QString::number(err));
            }
            return;
        }

        switch(comData.at(2) & 0xFF)
        {
            case 0x00://数据ack
            if(!(comData.at(5) & 0xFF))
            {//数据正确发送，现在发送下一帧
                downloadedByte += curDownloadDataLen;
                undownloadByte -= curDownloadDataLen;
                if(undownloadByte == 0)//下载完成
                {
                    /*发送结束指令*/
                    send_end_signal();
                }
                else
                {
                    ui->downloadingBar->setValue((downloadedByte * 100) / binDataLen); //设置进度条

                    ui->hasSendSizeLab->setText(QString::number(downloadedByte)); //显示已发送大小
                    emit(ackOK());
                }
            }
            else//校验有误，重新发送当前数据
            {
                sendErrTimes++;
                qInfo()<<"发送的数据有未知错误，现在重新发送";
                ui->sendMsgDisEdt->append(QString("发送 地址[ %1 ] to [ %2 ] 的数据出错！现在重新发送").arg(downloadedByte).arg(downloadedByte + curDownloadDataLen - 1));
                emit(ackOK());//此处不改变downloadedByte和undownloadByte的值直接重新发送
            }
            break;
            case 0x01://开始信号ack
            if(!(comData.at(5) & 0xFF))
            {
                ui->receiveAckEdt->append(current_time + " : 下位机已准备好，可以开始传输bin");
                downloadState = DOWNLOAD_START_ACK;
                ui->startDownloadBtn->setDisabled(true);
                /*重置一些数据*/
                undownloadByte = binDataLen;
                downloadedByte = 0;
                sendErrTimes = 0;
                ui->downloadingBar->setValue((downloadedByte * 100) / binDataLen);
                emit(ackOK());
            }
            else
            {
                downloadState = DOWNLOAD_INVALID;//下载状态置位无效
                if((comData.at(5) & 0xFF) == 0x01)//硬件出错
                {
                    ui->receiveAckEdt->append(current_time + " :开始下载数据（硬件出错）");
                }
                else if((comData.at(5) & 0xFF) == 0x02)//校验码出错
                {
                    ui->receiveAckEdt->append(current_time + " : 开始下载数据（校验码出错）");
                }
                else if((comData.at(5) & 0xFF) == 0x03)//数据长度有误
                {
                    ui->receiveAckEdt->append(current_time + " : 开始下载数据（数据长度有误）");
                }
                else
                {
                    ui->receiveAckEdt->append(current_time + " : 开始下载数据（未知错误）");
                }
            }
            break;
            case 0x02:  //结束
                if(!(comData.at(5) & 0xFF))
                {
                    sendDataFinish();
                    downloadState = DOWNLOAD_FINISH;
                }
            break;
            case 0xff:  //未知错误
                qInfo()<<"err code:0XFF ";
                sendErrTimes++;
                ui->sendMsgDisEdt->append(QString("发送 地址[ %1 ] to [ %2 ] 的数据出错！现在重新发送").arg(downloadedByte).arg(downloadedByte + curDownloadDataLen - 1));
                emit(ackOK());//此处不改变downloadedByte和undownloadByte的值直接重新发送
            break;
            default:
            qInfo()<<"err code:default ";
            break;
        }
    }
}
/*发送一次固定大小的数据包*/
void MainWindow::sendOnceData()
{
    QByteArray dataBuf, sendBuf;
    uint8_t fileSizeHigh = 0,fileSizeLow = 0;
    uint8_t bcc = 0x00;
    QString showStr;
//    qInfo()<<"send once data,size:"<<dataOnceLen<<"(Byte) dowloaded :"<<downloadedByte<<"(Byte) residue:"<<undownloadByte<<"(Byte)";


    if(undownloadByte > dataOnceLen)
    {
        dataBuf = binDataArray.mid(downloadedByte, dataOnceLen);//读取一段数据
        curDownloadDataLen =  dataOnceLen;
    }
    else//发送最后一点不足一个数据包大小的数据
    {
        dataBuf = binDataArray.mid(downloadedByte, dataOnceLen);//读取一段数据
        curDownloadDataLen =  undownloadByte;
    }
    sendBuf.clear();
    sendBuf.append("\xC5\x5C"); //添加数据头
//    sendBuf.append("\x00");     //00表示传输的是数据
    sendBuf.append(static_cast<char>(0));   //00表示传输的是数据
    fileSizeHigh = curDownloadDataLen / 256;
    fileSizeLow = curDownloadDataLen % 256;
    sendBuf.append(fileSizeHigh);//添加数据长度
    sendBuf.append(fileSizeLow);

    sendBuf.append(dataBuf);    //要发送的数据
    /*求校验位*/
    for(int i = 2; i < sendBuf.size(); i++)
    {
        bcc ^= sendBuf[i];
    }
    sendBuf.append(bcc);        //添加校验位
    sendBuf.append("\x5C\xC5"); //添加数据尾
    mySerialPort.write(sendBuf);//发送数据

    for(int i = 0; i < sendBuf.size(); i++){
        showStr.append((QString("%1  ").arg(sendBuf.at(i) & 0xff, 2, 16, QLatin1Char('0')).toUpper()));
    }
    ui->sendMsgDisEdt->append("---------------------------------------");
    ui->sendMsgDisEdt->append(QString("地址[ %1 ] to [ %2 ] 的数据：").arg(downloadedByte).arg(downloadedByte + curDownloadDataLen - 1));
    ui->sendMsgDisEdt->append(showStr);
    ui->sendMsgDisEdt->append(QString("---  BCC校验码: %1  ---").arg(bcc & 0xff, 2, 16, QLatin1Char('0')).toUpper());

    ui->sendMsgDisEdt->append(QString("send [ %1 ] to [ %2 ] Data ...").arg(downloadedByte).arg(downloadedByte + curDownloadDataLen - 1));
    downloadState = DOWNLOAD_DATA_WAIT_ACK;
}
/*发送结束*/
void MainWindow::sendDataFinish()
{
    QDateTime current_date_time = QDateTime::currentDateTime();//获取系统时间
    QString current_time = current_date_time.toString("hh:mm:ss");

    ui->startDownloadBtn->setDisabled(false);
    ui->downloadingBar->setValue(100);
    ui->receiveAckEdt->append(current_time + QString("下载结束，共发送 %1 Byte 错误次数：%2").arg(downloadedByte).arg(sendErrTimes));
}

